/**
 * Auteur : Dominique GUERIN
 * dominiquephDOTguerinATgmailDOT..
 * dominiqueDOTguerinATinseeDOTfr(ance)  
 * Remerciements: � Keith Brown pour ses deux livres sur la s�curit� sous Windows et ses diff�rents articles
 *                 et � Michel Barnett de Microsoft pour ses deux articles 
 *                ".NET Remoting Authentication and Authorization Sample"  et le code qui les accompagne
 *  Le code est utilisable, modifiable et redistribuable � volont� sous la condition de ne pas supprimer ces 7 lignes.
 */ 

package fr.doume.tcp.sspi;


//tomcat 5.5
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//tomcat 6
//import org.apache.juli.logging.Log;
//import org.apache.juli.logging.LogFactory;

import fr.doume.tcp.sspi.SSPException;
import fr.doume.tcp.sspi.TranslationRolesIntoSids;
import fr.doume.tcp.sspi.ConnectionTcpPool;

import java.io.ByteArrayOutputStream;

import fr.doume.tcp.authenticator.Parameters;
import fr.doume.tcp.authenticator.AuthenticationContext;

import java.net.Socket;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

import java.net.InetAddress;
import java.net.InetSocketAddress;

import java.io.DataInputStream;
import java.io.DataOutputStream;

import java.io.IOException;

import java.util.Timer;
import java.util.TimerTask;

import java.util.Date;

import javax.net.ssl.SSLSocketFactory;

/**
* Wraps  the exchanges with NegoServer .
  */
public class SSPAuthentication
{
	private static Log log = LogFactory.getLog(SSPAuthentication.class);
	

 /**
  *  State send by NegotiateStream.<br>
  * <ol>Three states : 
  * <li> ERROR (not authenticated) (2),
  * <li> 0 ( authenticated ), 
  * <li> CONTINUE_NEEDED (1) (another exchange bettween the client and the server is required, NTLM).
  * </ol>
  */ 
	private static final int CONTINUE_NEEDED = 1;
	private static final int ERROR = 2;
	private static final int NOTAUTHENTICATEDUSER = 3;

  /**
   * Time-out used by the connection and the calls to Read and Write of the socket
   */
  private static final int TIME_OUT_MS =8000;

  /**
   * Status code send by NegotioateStream in Negoserver ( [MS-NSS].pdf )
   */
  private static final byte HANDSHAKEDONE= 0x14;

    /**
    * Status code send by NegotioateStream in Negoserver ( [MS-NSS].pdf )
   */
  private static final byte HANDSHAKEERROR= 0x15;

    /**
   * Status code send by NegotioateStream in Negoserver ( [MS-NSS].pdf )
   */
  private static final byte HANDSHAKEINPROGRESS= 0x16;

  
	/**
	* status code send by NegoServer
	*/
	private long failed;
	/**
	* Is NegotiateStream waiting other bytes ?
	*/
	private long continue_needed;
	
	private boolean isContextSet;
	
	private boolean existUserName;	
	private String userName = null;
  /**
  * Name of the Security Service Provider(NTLM or Kerberos)
  */
  private String SSP = null;
	private boolean existSSP;

  /**
   * True if the method communicateWithNegoServer has been already called (If yes => NTLM)
   */
	private boolean already_called;
	
	/**
	* Instance given to the constructor by the AuthenticationContext
  * This instance has the Sids of the roles of tomcat.
	*/ 
	private TranslationRolesIntoSids translationTomcatRolesiIntoSids;
	
	private AuthenticationContext authenticationContext;

	private Parameters param;
	
	/**
	*Sids from de translationTomcatRolesiIntoSids
	*/
	private byte[] sids;
  /**
  * Sent by the the NegoServer service. If a SID is in the access token of the client, 
  * the element of the array is set to 1, otherwise to 0 
  */
	//private byte[] groupsInAccessToken;
	private boolean[] groupsInAccessToken;
	
	/**
	 * Socket to the connection to NegoServer. Can be reused , when NTLM is the Security Service Privider.
	 */ 
	private Socket s = null;
	private InetAddress serverAddress;
	private int port;
  private Timer stop = null;
  
  private CancelConnectionForNTLM timeLifeSocketForNtlm = null;
	
    public SSPAuthentication(AuthenticationContext ac){
		failed = 0;
		continue_needed = 0;
		isContextSet = false;
		existUserName = false;
		existSSP = false;
		already_called = false;
		authenticationContext = ac;
		translationTomcatRolesiIntoSids = ac.getTranslationRolesIntoSids();
		param = ac.getParameters();
		s = null;
		port = 0;
		serverAddress = null;
		groupsInAccessToken = new boolean[0];
		log.debug("**********************************************New SSPAuthentification!!!");
	}
	/**
	* Receives the bytes sent by the client et returns bytes to send to the client.
	* Calls the method communicateWithNegoServer.
	*/ 
	public byte[] acceptSecContext( byte[] token)
	throws SSPException
	{
		if (isContextSet){
			throw new SSPException("The SSPI/GSSAPIcontext is already established ");
		}
		if(token == null){
      throw new SSPException("The array sent by the client is null");
    }
		if (token.length == 0){
			throw new SSPException("The length of the array sent by the client is 0");
		}
		if ( failed != 0){
				throw new SSPException("SSPAuthentification already in error");
		}

		String msg = "";
		String user = "";
		String l_SSP = "";

		byte[] tokenToClient = new byte[1]; tokenToClient[0] = 0;
    
    int sidsCount = 0;

		if (! already_called )
		{
      if ( ! param.areGroupsInAd() ){
				groupsInAccessToken = new boolean[0]; //new byte[0];
      }
      else{ //param.areGroupsInAd()
        try
        {
          //if ( param.areGroupsInAd())
          {
            sidsCount = translationTomcatRolesiIntoSids.getSidsCount();
            groupsInAccessToken = new boolean[sidsCount];
            if (log.isDebugEnabled()) log.debug("Count of sids given by TraduireNomsEnSids : " + sidsCount);
          }
        }
        catch (SSPException e)
        {
          if (log.isDebugEnabled()) log.debug("Count of Tomcat's roles is 0 : " +e.getMessage());
          groupsInAccessToken = new boolean[0]; //new byte[0];
        }
      }
    }

    byte [][] reftokenToClient= new byte[1][0];
    reftokenToClient[0] = tokenToClient;
    
    int status = communicateWithNegoServer(token, reftokenToClient);

    tokenToClient = reftokenToClient[0];
    
    l_SSP ="OUF!";
    user ="DOUME";
    already_called = true;
    msg = "Error between tomcat and NegotiateStream!";

    if (log.isDebugEnabled()) log.debug("Cont of bytes of tokenToClient : " +tokenToClient.length );

	
		if ( status == ERROR )
		{//Erreur
			failed = 1;
			continue_needed = 0;
			isContextSet = false;
			already_called = false;
			existUserName = false;
			existSSP = false;
			if (log.isDebugEnabled()) log.debug("Error in AcceptSecContext : " + msg );
			throw new SSPException(msg);
		}
		
		if ( status == NOTAUTHENTICATEDUSER )
		{
			failed = 1;
			continue_needed = 0;
			isContextSet = false;
			already_called = false;
			existUserName = false;
			existSSP = false;
			//if (log.isDebugEnabled()) log.debug("User is not a member of the authenticated Users " );
			throw new SSPException("User is not a member of the authenticated Users ");
		}
		
		//no error

		if (status == 0){ //Contexte etabli
			failed = 0;
			continue_needed = 0;
			isContextSet = true;
			existUserName = true;
			if (userName == null) userName = user;
			existSSP = true;
			if (SSP == null) SSP = l_SSP;
      if ( param.areGroupsInAd())
      {
        if (log.isDebugEnabled())
        {
          log.debug("Count of Sids translated by translationTomcatRolesiIntoSids : " + translationTomcatRolesiIntoSids.getSidsCount());  
          if (groupsInAccessToken.length != 0)
          {
            for (int i = 0; i < groupsInAccessToken.length; i++)
            {
              log.debug(" group number " + i + " = " + groupsInAccessToken[i] ); // + " (1 if in the access token, otherwise 0)");
            }
          }
          else
          {
            log.debug("the lenth of groupsInAccessToken is  0");
          }
        }
			}
		}
		else
		{//continue_needed
			failed = 0;
			continue_needed = 0;
			isContextSet = false;
			existUserName = false;
			existSSP = false;
		}

		return tokenToClient;
	}

	/**
	* Is client authentification estabblished ?
	*/
	public boolean isEstablished()
	{
		if (isContextSet){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	* Is client authentification failed ?
	*/
	public boolean isFailed()
	{
    if (failed == 0) return false;
    else return true;
	}
	
  /**
  * Returns the name of the client when the authentication is established
  */
	public String getUserName()	
	throws SSPException
	{
		if (existUserName){
			return userName;
		}
		else{
			throw new SSPException("User's name is unknown");
		}
	}
  /** Returns the Security Service Provider used by the authentication 
  * (NTLM or Kerberos) . Only for logs
  */
	public String getSSP()	
	throws SSPException
	{
		if (existSSP)
		{
			return SSP;
		}
		else
		{
			throw new SSPException("The name of the Security Service Provider Interface is unknown");
		}
	}

  /**
  * Returns a array of bytes given by Negoserver. 
  */
	public boolean[] areRolesInAccessToken()
	{
		//Todo verification
		return groupsInAccessToken;
	}

	private void closeSocket()
	{
	   if (s != null)
	   {
       try {s.close();}
       catch(Exception e){}
       s = null;
	   }
	}
	/**
	 * Cancel the timer
	 */
	private void cancelstop()
	{
	      ///*
      try
      {
        if (stop != null)
        {
          stop.cancel();
        }
      }
      catch(IllegalStateException e){}
      catch(IllegalArgumentException e){}
      finally
      {
        stop = null;
      }
      //*/

	}
	
	private void closeSocketAndCancelStop()
	{
    closeSocket();
    cancelstop();
	}
	/**
	* Manage the connection with Negoserver. Send and receives the bytes from and to SSPI, get the name of the client, the name of the SSP,
	* and the roles of tomcat that are in the access token of the client.
	* The specification of NegotiateStream is in [MS-NNs].pdf. NegotiateStream implements SPNEGO in dotnet. 
	* SSPAuthentication is conformant to this specification, so add 5 bytes in a header(version, length,status) cf class Prefix   
	*/
  private int communicateWithNegoServer( byte[]token, byte[][] reftokenToClient)
  {
    int val_temp;
    byte[] data_prefix = new byte[5]; 
    int size_prefix = 5; // cf [MS-NNs].pdf : specification de NegotiateStream
    //ConnectionTcpPool pool = null;

    //int from = 0;

    InputStream in = null;
    OutputStream out = null;


    
    ByteArrayOutputStream buf = new ByteArrayOutputStream(token.length + size_prefix + 2);

    byte[] two = new byte[1];
    two[0] = 2;  //ConnectionType.ReuseAConnectionTcp (cf NegoServer ConnectionType)
    buf.write(two, 0, 1);        
    
    //If we can accept a guest user
    //byte[] one = new byte[1];
    //one[0] = 1;   //ConnectionType.Authentication  (cf NegoServer ConnectionType)
    //buf.write(one, 0, 1); 
    
    byte[] three = new byte[1];
    three[0] = 3;  //ConnectionType.AuthenticationAndInformIfAuthenticatedUser  (cf NegoServer ConnectionType)
    buf.write(three, 0, 1); 
    
    //Add a byte: AUthenticated User only?
   
    if (log.isDebugEnabled()) log.debug("token length :" + token.length);
    Prefix prefix = new Prefix(token.length);

    buf.write(prefix.getData(), 0, size_prefix);
    buf.write(token, 0, token.length);
   
    ConnectionTcpPool pool = authenticationContext.getSocketsPool();
    try
    {
      out = sendTokenToNegotiateStream(buf, pool);
      in = receiveTokenFromNegotiateStream( data_prefix, pool, reftokenToClient);
      prefix = new Prefix( data_prefix);
      
      if ( prefix.getStatus() == HANDSHAKEERROR)
      {
         //error
        closeSocket();
        return ERROR;
      } 
      
      if (prefix.getStatus() == HANDSHAKEINPROGRESS) 
      {
        //firtst round trip: NTLM
        timeLifeSocketForNtlm = new CancelConnectionForNTLM(s);
        return CONTINUE_NEEDED;
      }
      else
      {
      
        // Read the name of the client and then, the SSP(Security Service Provider) : NTLM or Kerberos
        readNameAndSSP(in);
        boolean authenticatedUser = isAnAuthenticatedUser(in);
        if ( authenticatedUser )
        {      
          if ( param.areGroupsInAd() )
          {
            getGroupsInAccessToken(in, out);
          }
          else
          {
            sendZeroToNegoserver(out);
          }
        }
        try
        {
          log.debug("add the connection into the pool");
          pool.add(s);
        }
        catch(Exception e)
        {
          log.debug(e.getMessage());
          closeSocket();
        }
        s  = null;
        if (authenticatedUser) return 0;
        else return NOTAUTHENTICATEDUSER;
      }          
    }
    catch(java.io.IOException e)
    {
      if (log.isDebugEnabled()) log.debug("Error in communicateWithNegoServer : " + e.getMessage());
      closeSocketAndCancelStop();
      return ERROR;      
    }
    catch(SSPException e)
    {
      if (log.isDebugEnabled()) log.debug("Error in communicateWithNegoServer : " + e.getMessage());
      closeSocketAndCancelStop();
      return ERROR;      
    }
 
    //closeSocket();
    //return ERROR;  
  }
  
  /*
  private OutputStream sendTokenToNegotiateStream( ByteArrayOutputStream buf, ConnectionTcpPool pool )
  throws IOException, SSPException 
  {
    int from = 0;
    OutputStream out = null;

    buf.flush();
    byte[] toSend = buf.toByteArray();
    
    
    if (already_called)
      //NTLM : second round trip
    {
      if (timeLifeSocketForNtlm != null)
      {
        timeLifeSocketForNtlm.cancel();
      }
      out = s.getOutputStream();
      from = 2;
      stop = new Timer(); // set Time-out...
      stop.schedule( new CancelConnection() , TIME_OUT_MS);
      //Send to NegotiateStream : prefixe + token
      out.write( toSend, from, toSend.length - from);       
    }
    else
    {
      s = pool.getSocket();
      while( s != null)
      {
        log.debug("Try to reuse the connection!!!");
        out = s.getOutputStream();
        try
        {
          stop = new Timer(); // arr�t de la socket au bout de TIME_OUT_MS seconde...
          stop.schedule( new CancelConnection() , TIME_OUT_MS);
        //send to NegotiateStream : prefixe + token
        //  but with 2 bytes before:  
        //    2 (Reuse a socket)
        //    1 (calls for Authentication) 
          out.write( toSend, 0, toSend.length);
          break;            
        }
        catch(IOException ex)
        {
          log.debug("Connection cannot be reused!!!");
          closeSocketAndCancelStop();
          s = pool.getSocket();
        }
      }
      if ( s == null)  // no socket in the pool
      {
        log.debug (" create a new connection!!");
        createNewConnection( param );
        out = s.getOutputStream();
        from = 1;
        stop = new Timer();
        stop.schedule( new CancelConnection() , TIME_OUT_MS);
        //send to NegotiateStream : prefix + token but before, with a byte set to 1 (Authentication) 
        //so NegoServer send the byte to NegotiateStream 
        out.write( toSend, from, toSend.length - from);
      }
    }
    cancelstop();
    return out;
 
  }
  */
  
  private InputStream receiveTokenFromNegotiateStream(byte[] data_prefix, ConnectionTcpPool pool, byte[][] reftokenToClient)
  throws IOException, SSPException
  {
    //Read the bytes sent by NegotiateStream
    
    //Read the prefix
    int size_prefix = 5; // cf [MS-NNs].pdf : specification de NegotiateStream
    InputStream in = s.getInputStream();
    int chunk_size = 0;
    int position = 0;
    int remaining_to_read_in_data = data_prefix.length;
    while ( position < size_prefix )
    {
      chunk_size = in.read(data_prefix, position, remaining_to_read_in_data);
      if ( chunk_size == -1 ) break;
      position += chunk_size;
      remaining_to_read_in_data -= chunk_size;
      if (log.isDebugEnabled()) log.debug("First loop position : " + position);
    }
     
    if (position != size_prefix) throw new SSPException(" Error in receiveTokenFromNegotiateStream (postion != size_prefix)" );  
    else
    {
      // get the length from the prefix
       
      Prefix prefix = new Prefix(data_prefix);
      int lengt_prefix = prefix.getLength();  
      byte[] token_toClient = new byte[lengt_prefix];
      if (log.isDebugEnabled()) log.debug("Size of the answer  " + lengt_prefix);
      
      // read token_toClient
      
      int remaining_to_read = lengt_prefix;
      position = 0;
      while (remaining_to_read > 0)
      {
        chunk_size = in.read(token_toClient, position, remaining_to_read);
        if (chunk_size == -1) break;
        position += chunk_size;
        remaining_to_read -= chunk_size;
        if (log.isDebugEnabled()) log.debug("Second loop position : " + position);
      }
      
      if (remaining_to_read > 0) throw new SSPException("Error in receiveTokenFromNegotiateStream: remaining _to_read != 0");
      if (prefix.getStatus() != HANDSHAKEERROR)
      {
        reftokenToClient[0] = token_toClient;
        if (log.isDebugEnabled()) log.debug("Sizeof tokenToClient : " + reftokenToClient[0].length );
      }
    }
    return in;
  }
  /**
  * Get the names of the client and of the Seurity Service Provider.
  * Called by the method <code>communicateWithNegoServer</code>
  */
  private void readNameAndSSP(InputStream input)
  throws IOException
  {
    if (log.isDebugEnabled()) log.debug("Try to get the name of the client and the Security Service Provider used");

    int len;
    StringBuilder sb;
    DataInputStream data = new DataInputStream( input);
    len = data.readShort();
    len &= 0xffff;
    if (log.isDebugEnabled()) log.debug("TTry to get the length of the name " + len);

    sb = new StringBuilder( len );
    for (int i = 0; i < len; i++)
    {
      sb.append(data.readChar());
    }
    userName = sb.toString();
    if (log.isDebugEnabled()) log.debug("User's name " + userName);

    len = data.readShort();
    len &= 0xffff;
    if (log.isDebugEnabled()) log.debug("Try to get the get the length of the Security Service Provider " + len);

    sb = new StringBuilder( len );
    for (int i = 0; i < len; i++)
    {
      sb.append(data.readChar());
    }
    SSP = sb.toString();
    if (log.isDebugEnabled()) log.debug("SSP " + SSP);
    
    //int auth = data.readShort();
    //if (auth == 1) log.debug("User is an Authenticated user");
    //else log.debug("User is NOT an authenticated user");

  }
  
  private boolean isAnAuthenticatedUser(InputStream input)
  throws IOException
  {
    DataInputStream data = new DataInputStream( input);

    int auth = data.readShort();
    if (auth == 1)
    {
      log.debug("User is an Authenticated user");
      return true;
    }
    else
    {
      log.debug("User is NOT an authenticated user");
      return false;
    }
  }

   /**
  * Get the tomcat roles wich are in the client access token obtained by the class <code>NegotiateStream</code>
  * of NegoServer
  */
  private void getGroupsInAccessToken( InputStream input, OutputStream output )
  throws SSPException
  {
    try
    {
      //Groups in AD
      
      short len_s;
      
      int groups_count = translationTomcatRolesiIntoSids.getSidsCount();
      if (log.isDebugEnabled()) log.debug("getGroupsInAccessToken ,groups_count : " + groups_count);
      
      if (log.isDebugEnabled()) log.debug("Before translationTomcatRolesiIntoSids.getSids()");
      byte[] sids = translationTomcatRolesiIntoSids.getSids();
      if (log.isDebugEnabled()) log.debug("After translationTomcatRolesiIntoSids.getSids()" + " ,sids.length : " + sids.length );
      
      // Send the array of sids
      // cf Negoserver and the java class TranlationRolesIntoSids.
      //sids contains the count of the elements and then each element.
      //Each element contains the length aof a SID and then the bytes of the SID

      stop = new Timer(); // Set the time-out...
      stop.schedule( new CancelConnection() , TIME_OUT_MS);
      output.write(sids, 0, sids.length);
      cancelstop();
      
      
      //read the length of the array, and then the bytes array: 
      //   1 The group is an the access token
      //   else the groupe is not in.
      DataInputStream data = new DataInputStream(input);
      len_s = data.readShort();
      if (log.isDebugEnabled()) log.debug("len_s : " + len_s);

      int size_to_read = len_s & 0xffff;
      
      //if(size_to_read == 0) throw new SSPException("Inconsistent data sent by the Windows Server : Count_of_groups != count_of_sids");
      
      if (log.isDebugEnabled()) log.debug("getGroupsInAccessToken ,size_to_read : " + size_to_read);

      if ( groups_count != size_to_read ) 
        throw new SSPException("Inconsistent data sent by the Windows Server : size of groups_present is inconsistent");
      
      groupsInAccessToken = new boolean[groups_count];
      //byte[] presents = new byte[groups_count];
      byte present;
      for (int i = 0; i < groups_count; i++)
      {
        present = data.readByte();
        if (present !=0 && present != 1) throw new SSPException("Inconsistent data sent by the Windows Server : group_present is not 0 or 1");
        if (present == 1 )  groupsInAccessToken[i] = true;
        else groupsInAccessToken[i] = false;
        //presents[i] = present;
      }
      //groupsInAccessToken = presents;
      
    }
    catch(IOException e)
    {
      throw new SSPException("Error: inconstent groups_presents sent by the Windows server : " + e.getMessage() );
    }
    
  }
  
  private void sendZeroToNegoserver(OutputStream output)
    throws SSPException
  {
    try
    {
        byte[] buffer = new byte[2]; 
        buffer[0] =(byte)0; buffer[1] = (byte)0;
        stop = new Timer(); // define Time-out...
        stop.schedule( new CancelConnection() , TIME_OUT_MS);
        //output the length 0
        output.write(buffer, 0, 2);
        cancelstop();
        return;
    }
    catch(IOException e)
    {
      throw new SSPException("Error: sendZeroToNegoserver : " + e.getMessage() );
    }

  }
  
  /**
  * Create a new connection, if an old cannot be used again
  */
  private void createNewConnection(Parameters p)
  throws IOException,SSPException
  {  
    InetAddress serverAddress;
    int authenticationport;
    InetSocketAddress socket_address;

    if (log.isDebugEnabled()) log.debug("To the authentication : new Socket");
    serverAddress = p.getInetAddressServer();
    authenticationport = p.getPort();
    socket_address  = new InetSocketAddress(serverAddress, authenticationport);

    s = new Socket();
    s.setSoTimeout(TIME_OUT_MS);
    s.connect(socket_address, TIME_OUT_MS);
    if (log.isDebugEnabled()) log.debug("Connection for authentication of the client ");

    String server = p.getWindowsServerName();
    if ( p.isSSLRequired() )
    {
    //SSL
      if (log.isDebugEnabled()) log.debug("SSLRequired");
      if (log.isDebugEnabled()) log.debug("server name : " + server);
      try
      {
        SSLSocketFactory factory = p.getSSLFactory();
        s = factory.createSocket(s, server, authenticationport,true);
        if (log.isDebugEnabled()) log.debug("SSL is used to send and receive the flow to authententication");
      }  
      catch(Exception e)
      { 
        throw new SSPException( "factory " +e.getMessage());
      }
    }
  }
  
  public void login (String userName, String pwd)
  throws SSPException
  {
    
    int result = dologin(userName, pwd);
    if (result == 0)
    {
      failed = 0;
			continue_needed = 0;
			isContextSet = true;
			already_called = false;
			existUserName = true;
			existSSP = true;
    }
    else
    {
      failed = 1;
			continue_needed = 0;
			isContextSet = false;
			already_called = false;
			existUserName = false;
			existSSP = false;      
    }
  }
  
  private int dologin (String userName, String password)
  throws SSPException
  {
    try
    {
      byte[] toSend = createBufferToSendLoginPwd(userName, password);
      
      ConnectionTcpPool pool = authenticationContext.getSocketsPool();

      OutputStream out = connectToNegoserverAndSend( toSend, pool );
      InputStream in = s.getInputStream();
      readNameAndSSP(in);

      boolean authenticatedUser = isAnAuthenticatedUser(in);
      if ( authenticatedUser )
      {      
        if ( param.areGroupsInAd() )
        {
          getGroupsInAccessToken(in, out);
        }
        else
        {
          sendZeroToNegoserver(out);
        }
      }
      try
      {
        log.debug("add the connection into the pool");
        pool.add(s);
      }
      catch(Exception e)
      {
        log.debug(e.getMessage());
        closeSocket();
      }
      s  = null;
      if (authenticatedUser) return 0;
      else return NOTAUTHENTICATEDUSER;
            
      
      
    }
    catch(IOException e)
    {
      if (log.isDebugEnabled()) log.debug("Error in communicateWithNegoServer : " + e.getMessage());
      closeSocketAndCancelStop();
      return ERROR;      
    }
    catch(SSPException e)
    {
      if (log.isDebugEnabled()) log.debug("Error in communicateWithNegoServer : " + e.getMessage());
      closeSocketAndCancelStop();
      return ERROR;      
    }
  }
  
  private byte[] createBufferToSendLoginPwd(String user, String pwd)
  throws IOException, SSPException
  {

    if ( user == null || user.length() == 0) throw new SSPException("user is null");
    int length_user = user.length();
    int length_pwd = pwd.length();
    int length_domain = 0;
    String domain = null;
    
    int position = user.indexOf('\\');
    if (position != -1 && length_user > position + 1 )
    {
      domain = user.substring(0, position);
      user = user.substring( position + 1);
      length_domain = domain.length();
      length_user = user.length();
    }
    else domain = "";
       
    
    long len_buf_char = 1+ (1 + length_user) + (1 + length_domain) +  (1 + length_pwd);

    if(len_buf_char > Short.MAX_VALUE)
    {
      throw new SSPException("The length sent must be smaller than Short.MAX_VALUE");
    }
    long len_buf_bytes = len_buf_char * 2;
    if (log.isDebugEnabled()) log.debug("sizeof name + password  sent to the windows server: " + len_buf_char);

    ByteArrayOutputStream buf = new ByteArrayOutputStream((int)len_buf_bytes);
    DataOutputStream data = new DataOutputStream( buf);
    data.writeByte(2);  // send 2 when a socket is reused ( (cf NegoServer ConnectionType.ReuseAConnectionTcp)
    data.writeByte(4);  // calls Negoserver with 0 : We calls Negoserver to do an authentication with login/password.  
                        //        (cf NegoServer ConnectionType.AuthenticationLogin)
    data.writeShort((short) (len_buf_char -1));
    data.writeShort( (short) (length_user) );
    data.writeChars( user);
    data.writeShort( (short) (length_domain) );// length of the domain
    if (length_domain != 0) data.writeChars( domain);
    data.writeShort( (short) (length_pwd) );
    data.writeChars( pwd );
    data.flush();
    byte[] toSend = buf.toByteArray();
    
    return toSend;

  }
  
  private OutputStream sendTokenToNegotiateStream( ByteArrayOutputStream buf, ConnectionTcpPool pool )
  throws IOException, SSPException 
  {
    int from = 0;
    OutputStream out = null;

    buf.flush();
    byte[] toSend = buf.toByteArray();
    
    
    if (already_called)
      //NTLM : second round trip
    {
      if (timeLifeSocketForNtlm != null)
      {
        timeLifeSocketForNtlm.cancel();
      }
      out = s.getOutputStream();
      from = 2;
      stop = new Timer(); // set Time-out...
      stop.schedule( new CancelConnection() , TIME_OUT_MS);
      //Send to NegotiateStream : prefixe + token
      out.write( toSend, from, toSend.length - from);
      cancelstop();       
    }
    else
    {
      out = connectToNegoserverAndSend(toSend, pool);
    }
    return out;
 
  }    // Connection
    
  public OutputStream connectToNegoserverAndSend( byte[] toSend, ConnectionTcpPool pool )
  throws IOException, SSPException
  {
    int from = 0;
    OutputStream out = null;

    s = pool.getSocket();
    while( s != null)
    {
      log.debug("Try to reuse the connection!!!");
      out = s.getOutputStream();
      try
      {
        stop = new Timer(); // arr�t de la socket au bout de TIME_OUT_MS seconde...
        stop.schedule( new CancelConnection() , TIME_OUT_MS);
      //send to NegotiateStream : prefixe + token
      //  but with 2 bytes before:  
      //    2 (Reuse a socket)
      //    1 (calls for Authentication) 
        out.write( toSend, 0, toSend.length);
        break;            
      }
      catch(IOException ex)
      {
        log.debug("Connection cannot be reused!!!");
        closeSocketAndCancelStop();
        s = pool.getSocket();
      }
    }
    if ( s == null)  // no socket in the pool
    {
      log.debug (" create a new connection!!");
      createNewConnection( param );
      out = s.getOutputStream();
      from = 1;
      stop = new Timer();
      stop.schedule( new CancelConnection() , TIME_OUT_MS);
      //send to NegotiateStream : prefix + token but before, with a byte set to 1 (Authentication) 
      //so NegoServer send the byte to NegotiateStream 
      out.write( toSend, from, toSend.length - from);
    }
    cancelstop();
    return out;
  
  } 

  /**
  * This prefix is defined in the specification of <code>NegotiateStream</code>
  * cf (http://msdn.microsoft.com/en-us/library/cc236723(PROT.10).aspx)
  * or the file[MS-NNS.pdf]
  * The data sent to and received from <code>NegotiateStream</code> have a prefix with a status, 
  * the version number (1) and length of the data (count of bytes). 
  */
  private class Prefix
  {
    byte[] data;
    public Prefix(byte[] pData)
    {
      data = pData;
    }
    public Prefix(byte status, int length)
    {
      data = new byte[5];
      setVersion();
      setStatus(status);
      setLength(length);
    }
    public Prefix(int length)
    {
      data = new byte[5];
      setVersion();
      setStatus(HANDSHAKEDONE);
      setLength(length);
    }

    public byte[] getData()
    {
      return data;
    }
    public int getLength()
    {
      int highLength = data[3];
      if (highLength < 0)
      {
        highLength = 256 + highLength;
      }
      highLength = highLength << 8;
      
      int lowLength = data[4];
      if (lowLength < 0)
      {
        lowLength = 256 + lowLength;
      }
      
      int length = lowLength + highLength;
      return length;
    }
    public byte getStatus()
    {
      return data[0];
    }
    private void setStatus(byte status)
    {
      data[0] = status;
    }
    private void setLength(int length)
    {
      int lowByte = length & 255;
      if(lowByte > 127)
      {
        lowByte = -(256 -lowByte);
      }
      data[4] = (byte)lowByte;
      
      length = length >> 8;
      int highByte = length & 255;
      if(highByte > 127)
      {
        highByte = -(256 - highByte);
      }
      data[3] = (byte)highByte;
    }
    private void setVersion()
    {
      data[1] = 1;
      data[2] = 0;
    }       
  }
  /**
  * Wraps the run method called by a timer.
  */
  protected class CancelConnection extends TimerTask
  {
   /**
   * Closes a socket
   */
    public void run()
    {
      if ( s!= null)
      {
          try
          {
            s.close();
          }
          catch(IOException e){}
          finally
          {
            s = null;          
          }
      }
    }
    
  }
  
 }


  /**
  * Wraps a class <c>CancelSocket</c>: 
  * Get a socket and defines a time out to close this socket is not reused to finish the authentication (NTLM)
  * Used by the class <c>SSPAuthentication</c>
  */

class CancelConnectionForNTLM
{
  private Socket s;
  private Timer kill;
  private static Log log = LogFactory.getLog(CancelConnectionForNTLM.class);
  
  public CancelConnectionForNTLM(Socket socket)
  {
    s = socket;
    kill = new Timer(); // arr�t de la socket au bout de 15 seconde...
    kill.schedule( new CancelSocket() , 15 * 1000);
    if (log.isDebugEnabled()) log.debug("A new CancelConnectionForNTLM");

  }
  /**
   * Cancel a timer
   */
  public void cancel()
  {
      try
      {
        if (kill != null)
        {
          kill.cancel();
          if (log.isDebugEnabled()) log.debug("A Timer for ConnectionForNTLM has been canceled");
        }
      }
      catch(IllegalStateException e){}
      catch(IllegalArgumentException e){}
      finally
      {
        kill = null;
      }
  }
  
    /**
  * Defines the method run called by a timer.
  */

  private class CancelSocket extends TimerTask
  {
   /**
   *  Closes the socket used by an NTLM authentication, if the authentication is not finished after a time out.
   */
    public void run()
    {
      if ( s!= null)
      {
          try
          {
            s.close();
            if (log.isDebugEnabled()) log.debug("A Connection ForNTLM has been closed" );
          }
          catch(IOException e){}
          finally
          {
            s = null;          
          }
      }
    }
    
  }

}
